package com.ecloud.common.data.transaction;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.sql.DataSource;

import org.apache.ibatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

import com.ecloud.common.data.dynamic.DataSourceContext;

/**
 * 动态数据源管理器 支持事务
 *
 * @author LiuGangQiang Create in 2021/07/13
 */
public class DynamicTransaction implements Transaction {
    private final static Logger LOGGER = LoggerFactory.getLogger(DynamicTransaction.class);

    /**
     * 数据源
     *
     * @author LiuGangQiang Create in 2021/07/18
     */
    private final DataSource dataSource;

    /**
     * 主数据源连接
     *
     * @author LiuGangQiang Create in 2021/07/18
     */
    private Connection connection;

    /**
     * 是否开启事务
     *
     * @author LiuGangQiang Create in 2021/07/18
     */
    private Boolean isConnectionTransactional;

    /**
     * 是否自动提交
     *
     * @author LiuGangQiang Create in 2021/07/18
     */
    private Boolean autoCommit;

    /**
     * 连接标识
     *
     * @author LiuGangQiang Create in 2021/07/19
     */
    private String identification;

    /**
     * 其他连接缓存
     *
     * @author LiuGangQiang Create in 2021/07/18
     */
    private ConcurrentMap<String, Connection> connections;

    /**
     * 构造器
     *
     * @author LiuGangQiang Create in 2021/07/13
     * @param dataSource 数据源
     */
    public DynamicTransaction(DataSource dataSource) {
        Assert.notNull(dataSource, "No DataSource specified");
        this.identification = DataSourceContext.getDataSource();
        this.dataSource = dataSource;
        connections = new ConcurrentHashMap<>();
    }

    /**
     * @see org.apache.ibatis.transaction.Transaction#getConnection()
     */
    @Override
    public Connection getConnection() throws SQLException {
        /* 获取当前生效的数据源标识 */
        String current = DataSourceContext.getDataSource();
        if (current.equals(this.identification)) {
            if (this.connection == null) {
                openConnection();
            }
            return this.connection;
        } else {
            /* 不是默认数据源，获取连接并设置属性 */
            if (!connections.containsKey(current)) {
                try {
                    Connection conn = this.dataSource.getConnection();
                    /* 自动提交属性和主数据源保持连接 */
                    conn.setAutoCommit(this.autoCommit);
                    connections.put(current, conn);
                } catch (SQLException ex) {
                    throw new CannotGetJdbcConnectionException("could not get jdbc connection", ex);
                }
            }
            return connections.get(current);
        }
    }

    /**
     * 打开连接
     *
     * @author LiuGangQiang Create in 2021/07/16
     * @throws SQLException
     */
    private void openConnection() throws SQLException {
        this.connection = DataSourceUtils.doGetConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
        if (this.isConnectionTransactional) {
            DataSourceUtils.releaseConnection(this.connection, this.dataSource);
            this.connection = this.dataSource.getConnection();
            this.connection.setAutoCommit(this.autoCommit);
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("jdbc connection [{}] will{}be managed by spring", this.connection, (this.isConnectionTransactional ? " " : " not "));
        }
    }

    /**
     * @see org.apache.ibatis.transaction.Transaction#commit()
     */
    @Override
    public void commit() throws SQLException {
        if (this.connection != null && this.isConnectionTransactional && !this.autoCommit) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("committing jdbc connection [{}]", this.connection);
            }
            this.connection.commit();
            for (Connection conn : connections.values()) {
                conn.commit();
            }
        }
    }

    /**
     * @see org.apache.ibatis.transaction.Transaction#rollback()
     */
    @Override
    public void rollback() throws SQLException {
        if (this.connection != null && this.isConnectionTransactional && !this.autoCommit) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("rolling back jdbc connection [{}]", this.connection);
            }
            this.connection.rollback();
            for (Connection conn : connections.values()) {
                conn.rollback();
            }
        }
    }

    /**
     * @see org.apache.ibatis.transaction.Transaction#close()
     */
    @Override
    public void close() throws SQLException {
        DataSourceUtils.releaseConnection(this.connection, this.dataSource);
        for (Connection conn : connections.values()) {
            DataSourceUtils.releaseConnection(conn, this.dataSource);
        }
    }

    /**
     * @see org.apache.ibatis.transaction.Transaction#getTimeout()
     */
    @Override
    public Integer getTimeout() throws SQLException {
        ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        if (holder != null && holder.hasTimeout()) {
            return holder.getTimeToLiveInSeconds();
        }
        return null;
    }
}
